import {vec2, vec3, vec4} from 'gl-matrix';

type line = number | [number, number];

function pointsToLine([x1, y1]: vec2, [x2, y2]: vec2): line {
  if (x1 === x2) {
    return x1;
  }
  let a = (y2 - y1) / (x2 - x1);
  let b = y2 - a * x2;
  return [a, b];
}

function getX(line: line, y: number) {
  if (typeof line === 'number') {
    return line;
  }
  // y = ax + b
  const [a, b] = line;
  return (y - b) / a;
}

let line = pointsToLine([0, 1], [1, 1]);

function toBarycentricCoordinate(
  points: number[][],
  target: number[],
) {
  const [[x1, y1], [x2, y2], [x3, y3]] = points;
  const [x, y] = target;
  let c = ((y1 - y2) * x + (x2 - x1) * y + x1 * y2 - x2 * y1) / ((y1 - y2) * x3 + (x2 - x1) * y3 + x1 * y2 - x2 * y1);
  let b = ((y1 - y3) * x + (x3 - x1) * y + x1 * y3 - x3 * y1) / ((y1 - y3) * x2 + (x3 - x1) * y2 + x1 * y3 - x3 * y1);
  let a = 1 - b - c;
  return [a, b, c];
}

export async function renderLine(
  buffer: ImageData,
  zBuffer: number[],
  points: number[][],
  colors: number[][],
) {
}

export async function renderTriangle(
  buffer: ImageData,
  points: number[][],
  colors: [vec4, vec4, vec4],
  ctx: CanvasRenderingContext2D,
  zBuffer: number[],
  [normal, normalView]: vec3[],
  extraData: {
    eyePos: vec3
    uv?: [vec2, vec2, vec2]
    texture?: ImageData
    pointsOfWorld?: number[][]
    gloss: number
  },
) {
  const {
    eyePos,
    uv,
    texture,
    pointsOfWorld,
    gloss,
  } = extraData;
  const {width, height} = ctx.canvas;
  const [x1, y1, z1] = points[0];
  const [x2, y2, z2] = points[1];
  const [x3, y3, z3] = points[2];
  
  let xMin = Math.max(Math.min(x1, x2, x3), -1);
  let xMax = Math.min(Math.max(x1, x2, x3), 1);
  let yMin = Math.max(Math.min(y1, y2, y3), -1);
  let yMax = Math.min(Math.max(y1, y2, y3), 1);
  
  function mapUvToPx([u, v]: number[]) {
    return [
      Math.floor((u + 1) / 2 * width),
      Math.floor((v + 1) / 2 * height),
    ];
  }
  
  function mapPxToUV([x, y]: number[]) {
    return [
      (x / width) * 2 - 1,
      (y / width) * 2 - 1,
    ];
  }
  
  const min = mapUvToPx([xMin, yMin]);
  const max = mapUvToPx([xMax, yMax]);
  
  function setRgba(y: number, x: number, r: number, g: number, b: number, a: number) {
    let i = 4 * (y * width + x);
    buffer.data[i] = r;
    buffer.data[i + 1] = g;
    buffer.data[i + 2] = b;
    buffer.data[i + 3] = a;
  }
  
  // let light = vec3.create();
  let light: vec3 = [1, 1, 1];
  vec3.normalize(light, light);
  
  function calcLight(ta: number, tb: number, tc: number, r: number, em: number, g: number, b: number) {
    // 漫反射光
    let diff = Math.max(0, vec3.dot(normal, light));
    
    // 高光
    let spec = 0;
    
    // 边缘光
    let rim = 0;
    
    if (pointsOfWorld) {
      const [
        [x1, y1, z1],
        [x2, y2, z2],
        [x3, y3, z3],
      ] = pointsOfWorld;
      let x = (x1 * ta * 255 + x2 * tb * 255 + x3 * tc * 255);
      let y = (y1 * ta * 255 + y2 * tb * 255 + y3 * tc * 255);
      let z = (z1 * ta * 255 + z2 * tb * 255 + z3 * tc * 255);
      
      let viewDir: vec3 = [0, 0, 0];
      vec3.subtract(viewDir, [x, y, z], eyePos);
      vec3.normalize(viewDir, viewDir);
      let h = vec3.create();
      vec3.add(h, viewDir, light);
      vec3.normalize(h, h);
      let doted = vec3.dot(h, normal);
      spec = Math.max(0, doted);
      spec = Math.pow(spec, gloss);
      
      function calcRimLight() {
        // let dot = Math.max(0, Math.abs( vec3.dot(viewDir, normal)));
        let dot = Math.abs(vec3.dot(viewDir, normal));
        if (dot < .01) {
          return 1;
        }
        return 0;
      }
      
      rim = calcRimLight();
      
    }
    
    r = rim > 0 ? 0 : r * (em + diff) + 255 * spec;
    g = rim > 0 ? 0 : g * (em + diff) + 255 * spec;
    b = rim > 0 ? 0 : b * (em + diff) + 255 * spec;
    
    
    return {r, g, b};
  }

// 逐像素扫描
  for (let y = min[1]; y <= max[1] + 1; y++) {
    for (let x = min[0]; x <= max[0] + 1; x++) {
      
      let [_x, _y] = mapPxToUV([x, y]);
      
      
      const [ta, tb, tc] = toBarycentricCoordinate(points, [_x, _y]);
      const _z = (z1 * ta + z2 * tb + z3 * tc);
      let zBuffIdx = y * width + x;
      let zBufferSaved = zBuffer[zBuffIdx];
      
      if (_z <= zBufferSaved) {
        if (ta >= 0 && tb >= 0 && tc >= 0) {
          const [
            [r1, g1, b1, a1],
            [r2, g2, b2, a2],
            [r3, g3, b3, a3],
          ] = colors;
          zBuffer[zBuffIdx] = _z;
          
          // 环境光
          let em = 0.1;
          
          let r = (r1 * ta * 255 + r2 * tb * 255 + r3 * tc * 255);
          let g = (g1 * ta * 255 + g2 * tb * 255 + g3 * tc * 255);
          let b = (b1 * ta * 255 + b2 * tb * 255 + b3 * tc * 255);
          let a = (a1 * ta * 255 + a2 * tb * 255 + a3 * tc * 255);
          const __ret = calcLight(ta, tb, tc, r, em, g, b);
          r = __ret.r;
          g = __ret.g;
          b = __ret.b;
          
          setRgba(y, x, r, g, b, a);
        }
      }
    }
  }
}
